package edu.pku.sei.mdabase.infrastructure.runtime;

import java.util.HashMap;
import java.util.Map;

import edu.pku.sei.mdabase.core.IDataType;
import edu.pku.sei.mdabase.core.INamedElement;
import edu.pku.sei.mdabase.infrastructure.graph.Graph;
import edu.pku.sei.mdabase.infrastructure.graph.GraphObject;
import edu.pku.sei.mdabase.infrastructure.model.MAttribute;
import edu.pku.sei.mdabase.infrastructure.model.MModelObject;

public class Context implements Cloneable{
	private Map<String,Variable> variables = null;
	private Context parentContext = null;
	private Environment environment;
	private Object key = null;
	
	public Object getKey()
	{
		return key;
	}
	
	public void setKey(Object key)
	{
		this.key = key;
	}
	
	public Map<String, Variable> getVariables() {
		if(variables==null)
			variables = new HashMap<String,Variable>();
		return variables;
	}
	
	public Variable getVariable(String name){
		Variable v =  getVariables().get(name);
		if(v==null&&parentContext!=null&&parentContext!=this)
			v = parentContext.getVariable(name);
		
		return v;
	}
	
	/**
	 * call Variable.setValue() to finish the operation
	 * @param name
	 * @param value
	 */
	public void setVariable(String name,Object value){
		Variable v = getVariable(name);
		if(v==null){
			System.out.println("Context/setVariable: You want to set a variable named "+name+", but it doesn't exist in the context");
			return;
		}
		v.setValue(value);
	}
	
	public Variable registVariable(String name,IDataType type,boolean isBound, String domainName){
		Variable v = null;
		
		v=getVariable(name);
		
		if(v==null){
			v = new Variable();
			v.setName(name);
			v.setType(type);
			v.setBoundVariable(isBound);
			v.setDomainName(domainName);
			getVariables().put(name, v);
		}
		return v;
	}
	
	public Variable registVariable(String name,IDataType type,boolean isBound){
		Variable v = null;
		
		v=getVariable(name);
		
		if(v==null){
			v = new Variable();
			v.setName(name);
			v.setType(type);
			v.setBoundVariable(isBound);
			v.setDomainName(null);
			getVariables().put(name, v);
		}
		return v;
	}
	
	public void removeVariable(String name){
		getVariables().put(name, null);
	}

	@Override
	public Context clone() {
		Context c = new Context();
		
		if(c != null){
			c.variables = new HashMap<String,Variable>();
			for(Variable v : getVariables().values()){
				c.variables.put(v.getName(), (Variable) v.clone());
			}
		}
		c.setKey(key);
		c.setEnvironment(environment);
		c.setParentContext(parentContext);
		
		return c;
	}
	
	public boolean isValid(){
		return isValid(Graph.BASE);
	}
	
	@SuppressWarnings("unchecked")
	public boolean isValid(Object key){
		Map<String,Variable> m = getVariables();
		for(Variable v : m.values()){
			Object value = v.getValue();
			if (value != null && value instanceof GraphObject)
				if (((GraphObject) value).getHost(key) == null)// deleted
					return false;

		}
		return true;
	}
	
	@Override
	public boolean equals(Object arg0) {
		if(arg0 == this)
			return true;
		if(arg0 instanceof Context == false)
			return false;
		Context c = (Context) arg0;
		for(Map.Entry<String,Variable> e : getVariables().entrySet()){
			Variable v1, v2;
			v1 = e.getValue();
			v2 = c.getVariable(e.getKey());
			if(v2==null)
				return false;
			if(v1.getValue().equals(v2.getValue())==false)
				return false;
		}
		return super.equals(arg0);
	}
	
	public boolean containsValue(Variable var)
	{
		//return variables.containsValue(var);
		for (Variable v : variables.values())
		{
			if (v.getValue() == var.getValue())
				return true;
		}
		return false;
	}

	public Context getParentContext() {
		return parentContext;
	}

	public void setParentContext(Context parentContext) {
		this.parentContext = parentContext;
	}

	public Environment getEnvironment() {
		if(environment==null&&parentContext!=null&&parentContext!=this){
			return parentContext.getEnvironment();
		}
		return environment;
	}

	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}
}
